package cc.lj.internal.coupons.infrastructure.repository.redis;

import cc.lj.internal.core.exception.ServiceException;
import cc.lj.internal.coupons.domain.ticket.model.vo.TicketBatchDO;
import cc.lj.internal.coupons.domain.ticket.repository.IUseTicketCheckCacheRepository;
import cc.lj.internal.coupons.infrastructure.excepion.CoupousExceptionEnum;
import cc.lj.internal.coupons.infrastructure.po.TicketMainPO;
import cc.lj.internal.coupons.interfaces.dto.command.DeleteUseTicketCheckCommand;
import cc.lj.internal.coupons.interfaces.dto.command.UseTicketCheckCommand;
import lj.internal.dict.enums.redis.EnumUseTicketCheckRedisKeys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 用券验证缓存数据 redis 仓储实现
 * @author liangxifeng
 * @date 2023/6/26 17:48
 */
@Repository("usetTicketCheckCacheRedisRepositoryImpl")
@Slf4j
public class UsetTicketCheckCacheRedisRepositoryImpl implements IUseTicketCheckCacheRepository {
    @Autowired
    private StringRedisTemplate redisTemplate;

    //redis key 过期时间3600秒（1小时）
    private long expireSecond = 3600;

    /**
     * 验证电子券是否已被使用
     * @param entityPO
     * @param useTicketCheckCommand
     */
    @Override
    public void checkTicketIsUse(TicketMainPO entityPO, UseTicketCheckCommand useTicketCheckCommand) {
        //后缀=合同号_uuid
        String suffix = useTicketCheckCommand.getPactNum()+"_"+useTicketCheckCommand.getUuId();
        //首先全局验证，看券是否正在被其他合同使用 (global_used_券使用码_合同号_UUID)
        String useTicketGlobalkey = EnumUseTicketCheckRedisKeys.GLOBAL_USED_TICKET_PREFIX.getKey()+ useTicketCheckCommand.getTicketUseCode()+"_*";
        //Integer batchId1 = Integer.valueOf(redisTemplate.opsForValue().get(useTicketGlobalkey)); //券批次主键
        //global_use_券使用码_ 为前缀的所有key的名称
        Set<String> keySet = redisTemplate.keys(useTicketGlobalkey);
        log.info("验证电子券是否在redis中存在：redis中全局券使用数据="+keySet.toString());
        /* 全局锁不做验证了
        if(!keySet.isEmpty()) {
            log.error("验证电子券是否在redis中存在：该使用码:"+useTicketCheckCommand.getTicketUseCode()+",存在于全局redis使用数据中. 不可用券!");
            throw new ServiceException(CoupousExceptionEnum.TICKET_CUR_USED);
        }*/
        //已用券列表key1 = use_ticket_合同号_UUID (HASH)
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+suffix;

        Object batchId2 = redisTemplate.opsForHash().get(useTicketkey,useTicketCheckCommand.getTicketUseCode());//券批次主键
        if(batchId2 != null ) {
            log.error("验证电子券是否在redis中存在：该使用码:"+useTicketCheckCommand.getTicketUseCode()+",存在于本合同已用券列表中. 不可用券!");
            throw new ServiceException(CoupousExceptionEnum.TICKET_CUR_USED);
        }
    }

    /**
     * 删除用券数据, 对应操作redis如下：
     * 1. key1 = use_ticket_合同号_UUID (HASH), value1 = {券使用码:券批次主键} 删除指定使用码
     * 2. key2 = use_batch_合同号_UUID(HASH), value2 =  {券批次主键 : 使用张数} 对应用券批次数量减-1
     * 3. key4 = global_used_券使用码_合同号_UUID(String 类型，目的：排除一张券在多个端用），value = 券批次主键，直接删除对应key
     * 4. key3 = batch_other_合同号_UUID (HASH) 如果券列表无任何数据了，则将该key3一起删除
     * @param useTicketCheckCommand
     */
    @Override
    public void deleteUseTicket(DeleteUseTicketCheckCommand useTicketCheckCommand) {
        //后缀=合同号_uuid
        String suffix = useTicketCheckCommand.getPactNum()+"_"+useTicketCheckCommand.getUuId();

        //1.删除已用券列表key1 = use_ticket_合同号_UUID (HASH)中的券使用码数据
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+suffix;
        Object batchId1 = redisTemplate.opsForHash().get(useTicketkey,useTicketCheckCommand.getTicketUseCode());
        if(batchId1 == null) {
            log.error("删除redis中已用券数据：key="+useTicketkey+",hashKey="+useTicketCheckCommand.getTicketUseCode()+",在redis中没有存在数据");
            throw new ServiceException(CoupousExceptionEnum.REDIS_USE_TICKET_NOT_DATA);
        }
        String batchId = batchId1.toString();
        log.info("删除redis中已用券数据：电子券batchId:"+batchId+",使用码："+useTicketCheckCommand.getTicketUseCode());
        redisTemplate.opsForHash().delete(useTicketkey,useTicketCheckCommand.getTicketUseCode());

        //2.删除全局券
        String useTicketGlobalkey = EnumUseTicketCheckRedisKeys.GLOBAL_USED_TICKET_PREFIX.getKey()+ useTicketCheckCommand.getTicketUseCode()+"_"+suffix;
        String batchId2 = redisTemplate.opsForValue().get(useTicketGlobalkey);
        if(batchId2 == null) {
            log.error("删除redis中已用券全局数据：key="+useTicketGlobalkey+",在redis中没有存在数据");
            throw new ServiceException(CoupousExceptionEnum.REDIS_USE_TICKET_NOT_DATA);
        }

        redisTemplate.delete(useTicketGlobalkey);

        //3.券批次数量-1
        String useBatchkey = EnumUseTicketCheckRedisKeys.USE_BATCH_PREFIX.getKey()+suffix;
        Object batchNumObj = redisTemplate.opsForHash().get(useBatchkey,batchId);//用券数量
        if(batchNumObj == null) {
            log.error("删除redis中已用券批次已使用券张数-1：key="+useBatchkey+", 在redis中没有存在数据");
            throw new ServiceException(CoupousExceptionEnum.REDIS_USE_BATCH_NOT_DATA);
        }
        Integer batchNum = Integer.valueOf(batchNumObj.toString());
        if(batchNum > 0)
        {
            //已用券批次主键自-1
            long batchTicketNum = redisTemplate.opsForHash().increment(useBatchkey,batchId,-1);

            if(batchTicketNum == 0) {
                //如果该券批次数量=0，则删除该批次在redis中的数据
                redisTemplate.opsForHash().delete(useBatchkey,batchId);
            }
        }
        //查询用券批次 hash map
        Map<Integer,Integer> useBatchMap = getUseBatch(useTicketCheckCommand.getPactNum(), useTicketCheckCommand.getUuId());
        if(useBatchMap.size() == 0) {
            //删除用券其他数据
            String otherHashKey = EnumUseTicketCheckRedisKeys.BATCH_OTHER_PREFIX.getKey() + suffix;
            redisTemplate.opsForHash().delete(otherHashKey,"single_batch_id","limit_value");
        }
    }

    /**
     * 新增用券列表数据
     * key1 = use_ticket_合同号_UUID (HASH), value1 = {券使用码:券批次主键}
     * key2 = use_batch_合同号_UUID(HASH), value2 =  {券批次主键 : 使用张数}
     * key4 = 券使用码_合同号_UUID(String 类型，目的：排除一张券在多个端用），value = 券批次主键
     */
    @Override
    public void addUseTicket(TicketMainPO entityPO, UseTicketCheckCommand useTicketCheckCommand) {
        //后缀=合同号_uuid
        String suffix = useTicketCheckCommand.getPactNum()+"_"+useTicketCheckCommand.getUuId();
        //1验证：电子券是否正在使用中
        checkTicketIsUse(entityPO,useTicketCheckCommand);

        //2设置：全局用券 (key= global_used_券使用码_合同号_UUID, value=电子券批次主键)
        String useTicketGlobalkey = EnumUseTicketCheckRedisKeys.GLOBAL_USED_TICKET_PREFIX.getKey()+ useTicketCheckCommand.getTicketUseCode()+"_"+suffix;
        redisTemplate.opsForValue().set(useTicketGlobalkey, String.valueOf(entityPO.getTicketBatchId()));
        redisTemplate.expire(useTicketGlobalkey, expireSecond, TimeUnit.SECONDS);//设置过期时间

        //3设置：已用券列表key = use_ticket_合同号_UUID (HASH), hashKey=券使用码， value=电子券批次主键
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+suffix;
        redisTemplate.opsForHash().put(useTicketkey,useTicketCheckCommand.getTicketUseCode(),entityPO.getTicketBatchId().toString());
        redisTemplate.expire(useTicketkey, expireSecond, TimeUnit.SECONDS);

        //4设置：券批次 key=use_batch_合同号_UUID(HASH), value2 =  {券批次主键 : 使用张数}
        String useBatchkey = EnumUseTicketCheckRedisKeys.USE_BATCH_PREFIX.getKey()+suffix;
        //已用券批次主键自增+1
        redisTemplate.opsForHash().increment(useBatchkey,entityPO.getTicketBatchId().toString(),1);
        redisTemplate.expire(useBatchkey, expireSecond, TimeUnit.SECONDS);
    }

    /**
     * 设置用券其他数据
     * key3 = batch_other_合同号_UUID (HASH)， value = { single_batch_id: 独立使用的券批次主键, limit_value: 满减剩余额度空间)
     * @param pactNum 合同号
     * @param uuId 唯一识别UUID
     * @param useBatchOther
     */
    @Override
    public void setUseTicketOther(String pactNum,String uuId,Map<String,Object> useBatchOther ) {
        String hashKey = EnumUseTicketCheckRedisKeys.BATCH_OTHER_PREFIX.getKey() + pactNum+ "_" + uuId;
        useBatchOther.forEach((field,value)->{
            if(field != null && value != null) {
                redisTemplate.opsForHash().put(hashKey,field,String.valueOf(value));
            }
        });
        //设置过期key时间
        redisTemplate.expire(hashKey, expireSecond, TimeUnit.SECONDS);
    }

    /**
     * 查询已用券列表 key = use_ticket_合同号_UUID (HASH)
     * @param pactNum 合同号
     * @param uuId UUID
     * @return Map<券使用码，券批次主键>
     */
    @Override
    public Map<String, Integer> getUseTicketList(String pactNum, String uuId) {
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+pactNum + "_" +uuId;
        Map<Object,Object> useTicketList = redisTemplate.opsForHash().entries(useTicketkey);
        Map<String,Integer> returnMap = new HashMap<>();
        //转换map key 和 value 类型
        for (Map.Entry<Object, Object> entry : useTicketList.entrySet()) {
            returnMap.put(entry.getKey().toString(),Integer.valueOf( entry.getValue().toString() ));
            //System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }
        return returnMap;
    }
    /**
     * 查询已用券批次信息 key=use_batch_合同号_UUID(HASH)
     * @param pactNum 合同号
     * @param uuId UUID
     * @return map{券批次主键 : 使用张数}
     */
    @Override
    public Map<Integer, Integer> getUseBatch(String pactNum, String uuId) {
        String hashKey = EnumUseTicketCheckRedisKeys.USE_BATCH_PREFIX.getKey()+pactNum + "_" +uuId;
        Map<Object,Object> batchHash = redisTemplate.opsForHash().entries(hashKey);
        Map<Integer,Integer> returnMap = new HashMap<>();
        //转换map key 和 value 类型为Integer
        for (Map.Entry<Object, Object> entry : batchHash.entrySet()) {
            returnMap.put(Integer.valueOf(entry.getKey().toString()),Integer.valueOf( entry.getValue().toString() ));
            //System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }
        return returnMap;
    }

    /**
     * 查询已用券其他数据 key3 = batch_other_合同号_UUID (HASH)
     * @param pactNum 合同号
     * @param uuId UUID
     * @return Map<String,Object> single_batch_id: 独立使用的券批次主键, limit_value: 满减剩余额度空间)
     */
    @Override
    public Map<String,Object> getUseTicketOther(String pactNum, String uuId) {
        String hashKey = EnumUseTicketCheckRedisKeys.BATCH_OTHER_PREFIX.getKey()+pactNum + "_" +uuId;
        Map<Object,Object> useOther = redisTemplate.opsForHash().entries(hashKey);
        Map<String,Object> returnMap = new HashMap<>();
        //转换map key 和 value 类型为Integer
        for (Map.Entry<Object, Object> entry : useOther.entrySet()) {
            returnMap.put(entry.getKey().toString().toString(), entry.getValue().toString() );
            //System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }
        return returnMap;
    }

    /**
     * 打印合同在redis中的所有已用券数据
     * key1 = use_ticket_合同号_UUID (HASH), value1 = {券使用码:券批次主键}
     * key2 = use_batch_合同号_UUID(HASH), value2 =  {券批次主键 : 使用张数}
     * key3 = batch_other_合同号_UUID (HASH)， value = { single_batch_id: 独立使用的券批次主键, limit_value: 满减剩余额度空间)
     * key4 = 券使用码_合同号_UUID(String 类型，目的：排除一张券在多个端用），value = 券批次主键
     * @param pactNum 合同号
     * @param uuId 全局唯一uuid
     */
    @Override
    public void printAll(String pactNum, String uuId) {
        log.info("++++++合同号:"+pactNum+",uuId:"+uuId+"在redis中所有已用数据打印start：++++++++");
        //后缀=合同号_uuid
        String suffix = pactNum+"_"+uuId;
        //打印key1数据
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+suffix;
        Map<Object,Object> useTicketList = redisTemplate.opsForHash().entries(useTicketkey);
        log.info("key1:hash{电子券使用码:券批次主键}，key="+useTicketkey+",value="+useTicketList);

        //打印key2数据
        String hashKey = EnumUseTicketCheckRedisKeys.USE_BATCH_PREFIX.getKey()+suffix;
        Map<Object,Object> batchHash = redisTemplate.opsForHash().entries(hashKey);
        log.info("key2:hash{券批次主键:使用张数}，key="+hashKey+",value="+batchHash);

        //打印key3 全局 使用码
        //首先全局验证，看券是否正在被其他合同使用 (global_used_券使用码_合同号_UUID)
        String useTicketGlobalkey = EnumUseTicketCheckRedisKeys.GLOBAL_USED_TICKET_PREFIX.getKey()+ "*";
        //global_use_券使用码_ 为前缀的所有key的名称
        Set<String> keySet = redisTemplate.keys(useTicketGlobalkey);
        log.info("key3:string{电子券使用码:券批次主键}，key="+useTicketGlobalkey+",所有模糊批次key值="+keySet);

        //打印key4
        String hashKeyOther = EnumUseTicketCheckRedisKeys.BATCH_OTHER_PREFIX.getKey()+suffix;
        Map<Object,Object> useOther = redisTemplate.opsForHash().entries(hashKeyOther);
        log.info("key4:hash{single_batch_id: 独立使用的券批次主键, limit_value: 满减剩余额度空间}，key="+hashKeyOther+",value="+useOther);
        log.info("++++++合同号:"+pactNum+",uuId:"+uuId+"在redis中所有已用数据打印--end：++++++++");
    }
    /**
     * 除本合同的所有用券数据
     * @param pactNum 合同号
     */
    @Override
    public void deleteAll(String pactNum) {
        //删除key1
        log.info("++++++合同号:"+pactNum+",删除redis中所有本合同的数据 start：++++++++");
        //后缀=合同号_uuid
        String suffix = pactNum+"*";
        //删除key1数据
        String useTicketkey = EnumUseTicketCheckRedisKeys.USE_TICKET_PREFIX.getKey()+suffix;
        Set<String> keySet1 = redisTemplate.keys(useTicketkey);
        if ( !keySet1.isEmpty()) {
            redisTemplate.delete(keySet1);
        }
        log.info("key1="+useTicketkey+"，已删除");

        //删除key2数据
        String hashKey = EnumUseTicketCheckRedisKeys.USE_BATCH_PREFIX.getKey()+suffix;
        Set<String> keySet2 = redisTemplate.keys(hashKey);
        if ( !keySet2.isEmpty()) {
            redisTemplate.delete(keySet2);
        }
        log.info("key2="+hashKey+"，已删除");

        //删除key3
        String hashKeyOther = EnumUseTicketCheckRedisKeys.BATCH_OTHER_PREFIX.getKey()+suffix;
        Set<String> keySet3 = redisTemplate.keys(hashKeyOther);
        if ( !keySet3.isEmpty()) {
            redisTemplate.delete(keySet3);
        }
        log.info("key3="+hashKeyOther+"，已删除");

        //删除key4数据 券使用码_合同号_UUID(String 类型，目的：排除一张券在多个端用），value = 券批次主键
        String useTicketGlobalkey = EnumUseTicketCheckRedisKeys.GLOBAL_USED_TICKET_PREFIX.getKey()+ "*_"+pactNum+"*";
        //global_use_券使用码_ 为前缀的所有key的名称
        Set<String> keyGlobalSet = redisTemplate.keys(useTicketGlobalkey);
        if ( !keyGlobalSet.isEmpty()) {
            redisTemplate.delete(keyGlobalSet);
        }
        log.info("key4="+useTicketGlobalkey+"，已删除");
        log.info("++++++合同号:"+pactNum+",删除redis中所有本合同的数据 end：++++++++");

    }
}
